home *** CD-ROM | disk | FTP | other *** search
/ Sprite 1984 - 1993 / Sprite 1984 - 1993.iso / src / lib / tk2.3 / dist / tkText.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-08-14  |  44.7 KB  |  1,509 lines

  1. /* 
  2.  * tkText.c --
  3.  *
  4.  *    This module provides a big chunk of the implementation of
  5.  *    multi-line editable text widgets for Tk.  Among other things,
  6.  *    it provides the Tcl command interfaces to text widgets and
  7.  *    the display code.  The B-tree representation of text is
  8.  *    implemented elsewhere.
  9.  *
  10.  * Copyright 1992 Regents of the University of California.
  11.  * Permission to use, copy, modify, and distribute this
  12.  * software and its documentation for any purpose and without
  13.  * fee is hereby granted, provided that the above copyright
  14.  * notice appear in all copies.  The University of California
  15.  * makes no representations about the suitability of this
  16.  * software for any purpose.  It is provided "as is" without
  17.  * express or implied warranty.
  18.  */
  19.  
  20. #ifndef lint
  21. static char rcsid[] = "$Header: /user6/ouster/wish/RCS/tkText.c,v 1.23 92/08/14 14:45:44 ouster Exp $ SPRITE (Berkeley)";
  22. #endif
  23.  
  24. #include "default.h"
  25. #include "tkConfig.h"
  26. #include "tk.h"
  27. #include "tkText.h"
  28.  
  29. /*
  30.  * Information used to parse text configuration options:
  31.  */
  32.  
  33. static Tk_ConfigSpec configSpecs[] = {
  34.     {TK_CONFIG_BORDER, "-background", "background", "Background",
  35.     DEF_TEXT_BG_COLOR, Tk_Offset(TkText, border), TK_CONFIG_COLOR_ONLY},
  36.     {TK_CONFIG_BORDER, "-background", "background", "Background",
  37.     DEF_TEXT_BG_MONO, Tk_Offset(TkText, border), TK_CONFIG_MONO_ONLY},
  38.     {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL,
  39.     (char *) NULL, 0, 0},
  40.     {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
  41.     (char *) NULL, 0, 0},
  42.     {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
  43.     DEF_TEXT_BORDER_WIDTH, Tk_Offset(TkText, borderWidth), 0},
  44.     {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
  45.     DEF_TEXT_CURSOR, Tk_Offset(TkText, cursor), TK_CONFIG_NULL_OK},
  46.     {TK_CONFIG_BOOLEAN, "-exportselection", "exportSelection",
  47.     "ExportSelection", DEF_TEXT_EXPORT_SELECTION,
  48.     Tk_Offset(TkText, exportSelection), 0},
  49.     {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
  50.     (char *) NULL, 0, 0},
  51.     {TK_CONFIG_FONT, "-font", "font", "Font",
  52.     DEF_TEXT_FONT, Tk_Offset(TkText, fontPtr), 0},
  53.     {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
  54.     DEF_TEXT_FG, Tk_Offset(TkText, fgColor), 0},
  55.     {TK_CONFIG_INT, "-height", "height", "Height",
  56.     DEF_TEXT_HEIGHT, Tk_Offset(TkText, height), 0},
  57.     {TK_CONFIG_BORDER, "-insertbackground", "insertBackground", "Foreground",
  58.     DEF_TEXT_INSERT_BG, Tk_Offset(TkText, insertBorder), 0},
  59.     {TK_CONFIG_PIXELS, "-insertborderwidth", "insertBorderWidth", "BorderWidth",
  60.     DEF_TEXT_INSERT_BD_COLOR, Tk_Offset(TkText, insertBorderWidth),
  61.     TK_CONFIG_COLOR_ONLY},
  62.     {TK_CONFIG_PIXELS, "-insertborderwidth", "insertBorderWidth", "BorderWidth",
  63.     DEF_TEXT_INSERT_BD_MONO, Tk_Offset(TkText, insertBorderWidth),
  64.     TK_CONFIG_MONO_ONLY},
  65.     {TK_CONFIG_INT, "-insertofftime", "insertOffTime", "OffTime",
  66.     DEF_TEXT_INSERT_OFF_TIME, Tk_Offset(TkText, insertOffTime), 0},
  67.     {TK_CONFIG_INT, "-insertontime", "insertOnTime", "OnTime",
  68.     DEF_TEXT_INSERT_ON_TIME, Tk_Offset(TkText, insertOnTime), 0},
  69.     {TK_CONFIG_PIXELS, "-insertwidth", "insertWidth", "InsertWidth",
  70.     DEF_TEXT_INSERT_WIDTH, Tk_Offset(TkText, insertWidth), 0},
  71.     {TK_CONFIG_PIXELS, "-padx", "padX", "Pad",
  72.     DEF_TEXT_PADX, Tk_Offset(TkText, padX), 0},
  73.     {TK_CONFIG_PIXELS, "-pady", "padY", "Pad",
  74.     DEF_TEXT_PADY, Tk_Offset(TkText, padY), 0},
  75.     {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
  76.     DEF_TEXT_RELIEF, Tk_Offset(TkText, relief), 0},
  77.     {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Foreground",
  78.     DEF_ENTRY_SELECT_COLOR, Tk_Offset(TkText, selBorder),
  79.     TK_CONFIG_COLOR_ONLY},
  80.     {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Foreground",
  81.     DEF_TEXT_SELECT_MONO, Tk_Offset(TkText, selBorder),
  82.     TK_CONFIG_MONO_ONLY},
  83.     {TK_CONFIG_PIXELS, "-selectborderwidth", "selectBorderWidth", "BorderWidth",
  84.     DEF_TEXT_SELECT_BD_COLOR, Tk_Offset(TkText, selBorderWidth),
  85.     TK_CONFIG_COLOR_ONLY},
  86.     {TK_CONFIG_PIXELS, "-selectborderwidth", "selectBorderWidth", "BorderWidth",
  87.     DEF_TEXT_SELECT_BD_MONO, Tk_Offset(TkText, selBorderWidth),
  88.     TK_CONFIG_MONO_ONLY},
  89.     {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background",
  90.     DEF_TEXT_SELECT_FG_COLOR, Tk_Offset(TkText, selFgColorPtr),
  91.     TK_CONFIG_COLOR_ONLY},
  92.     {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background",
  93.     DEF_TEXT_SELECT_FG_MONO, Tk_Offset(TkText, selFgColorPtr),
  94.     TK_CONFIG_MONO_ONLY},
  95.     {TK_CONFIG_BOOLEAN, "-setgrid", "setGrid", "SetGrid",
  96.     DEF_TEXT_SET_GRID, Tk_Offset(TkText, setGrid), 0},
  97.     {TK_CONFIG_UID, "-state", "state", "State",
  98.     DEF_TEXT_STATE, Tk_Offset(TkText, state), 0},
  99.     {TK_CONFIG_INT, "-width", "width", "Width",
  100.     DEF_TEXT_WIDTH, Tk_Offset(TkText, width), 0},
  101.     {TK_CONFIG_UID, "-wrap", "wrap", "Wrap",
  102.     DEF_TEXT_WRAP, Tk_Offset(TkText, wrapMode), 0},
  103.     {TK_CONFIG_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
  104.     DEF_TEXT_YSCROLL_COMMAND, Tk_Offset(TkText, yScrollCmd),
  105.     TK_CONFIG_NULL_OK},
  106.     {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
  107.     (char *) NULL, 0, 0}
  108. };
  109.  
  110. /*
  111.  * The following definition specifies the maximum number of characters
  112.  * needed in a string to hold a position specifier.
  113.  */
  114.  
  115. #define POS_CHARS 30
  116.  
  117. /*
  118.  * Tk_Uid's used to represent text states:
  119.  */
  120.  
  121. Tk_Uid tkTextCharUid = NULL;
  122. Tk_Uid tkTextDisabledUid = NULL;
  123. Tk_Uid tkTextNoneUid = NULL;
  124. Tk_Uid tkTextNormalUid = NULL;
  125. Tk_Uid tkTextWordUid = NULL;
  126.  
  127. /*
  128.  * Forward declarations for procedures defined later in this file:
  129.  */
  130.  
  131. static int        ConfigureText _ANSI_ARGS_((Tcl_Interp *interp,
  132.                 TkText *textPtr, int argc, char **argv, int flags));
  133. static void        DeleteChars _ANSI_ARGS_((TkText *textPtr, int line1,
  134.                 int ch1, int line2, int ch2));
  135. static void        DestroyText _ANSI_ARGS_((ClientData clientData));
  136. static void        InsertChars _ANSI_ARGS_((TkText *textPtr, int line,
  137.                 int ch, char *string));
  138. static void        TextBlinkProc _ANSI_ARGS_((ClientData clientData));
  139. static void        TextEventProc _ANSI_ARGS_((ClientData clientData,
  140.                 XEvent *eventPtr));
  141. static int        TextFetchSelection _ANSI_ARGS_((ClientData clientData,
  142.                 int offset, char *buffer, int maxBytes));
  143. static void        TextFocusProc _ANSI_ARGS_((ClientData clientData,
  144.                 int gotFocus));
  145. static int        TextMarkCmd _ANSI_ARGS_((TkText *textPtr,
  146.                 Tcl_Interp *interp, int argc, char **argv));
  147. static int        TextScanCmd _ANSI_ARGS_((TkText *textPtr,
  148.                 Tcl_Interp *interp, int argc, char **argv));
  149. static int        TextWidgetCmd _ANSI_ARGS_((ClientData clientData,
  150.                 Tcl_Interp *interp, int argc, char **argv));
  151.  
  152. /*
  153.  *--------------------------------------------------------------
  154.  *
  155.  * Tk_TextCmd --
  156.  *
  157.  *    This procedure is invoked to process the "text" Tcl command.
  158.  *    See the user documentation for details on what it does.
  159.  *
  160.  * Results:
  161.  *    A standard Tcl result.
  162.  *
  163.  * Side effects:
  164.  *    See the user documentation.
  165.  *
  166.  *--------------------------------------------------------------
  167.  */
  168.  
  169. int
  170. Tk_TextCmd(clientData, interp, argc, argv)
  171.     ClientData clientData;    /* Main window associated with
  172.                  * interpreter. */
  173.     Tcl_Interp *interp;        /* Current interpreter. */
  174.     int argc;            /* Number of arguments. */
  175.     char **argv;        /* Argument strings. */
  176. {
  177.     Tk_Window tkwin = (Tk_Window) clientData;
  178.     Tk_Window new;
  179.     register TkText *textPtr;
  180.  
  181.     if (argc < 2) {
  182.     Tcl_AppendResult(interp, "wrong # args: should be \"",
  183.         argv[0], " pathName ?options?\"", (char *) NULL);
  184.     return TCL_ERROR;
  185.     }
  186.  
  187.     /*
  188.      * Perform once-only initialization:
  189.      */
  190.  
  191.     if (tkTextNormalUid == NULL) {
  192.     tkTextCharUid = Tk_GetUid("char");
  193.     tkTextDisabledUid = Tk_GetUid("disabled");
  194.     tkTextNoneUid = Tk_GetUid("none");
  195.     tkTextNormalUid = Tk_GetUid("normal");
  196.     tkTextWordUid = Tk_GetUid("word");
  197.     }
  198.  
  199.     /*
  200.      * Create the window.
  201.      */
  202.  
  203.     new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], (char *) NULL);
  204.     if (new == NULL) {
  205.     return TCL_ERROR;
  206.     }
  207.  
  208.     textPtr = (TkText *) ckalloc(sizeof(TkText));
  209.     textPtr->tkwin = new;
  210.     textPtr->interp = interp;
  211.     textPtr->tree = TkBTreeCreate();
  212.     Tcl_InitHashTable(&textPtr->tagTable, TCL_STRING_KEYS);
  213.     textPtr->numTags = 0;
  214.     Tcl_InitHashTable(&textPtr->markTable, TCL_STRING_KEYS);
  215.     textPtr->state = tkTextNormalUid;
  216.     textPtr->border = NULL;
  217.     textPtr->cursor = None;
  218.     textPtr->fgColor = NULL;
  219.     textPtr->fontPtr = NULL;
  220.     textPtr->prevWidth = Tk_Width(new);
  221.     textPtr->prevHeight = Tk_Height(new);
  222.     textPtr->topLinePtr = NULL;
  223.     TkTextCreateDInfo(textPtr);
  224.     TkTextSetView(textPtr, 0, 0);
  225.     textPtr->selBorder = NULL;
  226.     textPtr->selFgColorPtr = NULL;
  227.     textPtr->exportSelection = 1;
  228.     textPtr->selOffset = -1;
  229.     textPtr->insertAnnotPtr = NULL;
  230.     textPtr->insertBorder = NULL;
  231.     textPtr->insertBlinkHandler = (Tk_TimerToken) NULL;
  232.     textPtr->bindingTable = NULL;
  233.     textPtr->pickEvent.type = LeaveNotify;
  234.     textPtr->yScrollCmd = NULL;
  235.     textPtr->scanMarkLine = 0;
  236.     textPtr->scanMarkY = 0;
  237.     textPtr->flags = 0;
  238.  
  239.     /*
  240.      * Create the "sel" tag and the "current" and "insert" marks.
  241.      */
  242.  
  243.     textPtr->selTagPtr = TkTextCreateTag(textPtr, "sel");
  244.     textPtr->selTagPtr->relief = TK_RELIEF_RAISED;
  245.     textPtr->currentAnnotPtr = TkTextSetMark(textPtr, "current", 0, 0);
  246.     textPtr->insertAnnotPtr = TkTextSetMark(textPtr, "insert", 0, 0);
  247.  
  248.     Tk_SetClass(new, "Text");
  249.     Tk_CreateEventHandler(textPtr->tkwin, ExposureMask|StructureNotifyMask,
  250.         TextEventProc, (ClientData) textPtr);
  251.     Tk_CreateEventHandler(textPtr->tkwin, KeyPressMask|KeyReleaseMask
  252.         |ButtonPressMask|ButtonReleaseMask|EnterWindowMask
  253.         |LeaveWindowMask|PointerMotionMask, TkTextBindProc,
  254.         (ClientData) textPtr);
  255.     Tk_CreateSelHandler(textPtr->tkwin, XA_STRING, TextFetchSelection,
  256.         (ClientData) textPtr, XA_STRING);
  257.     Tcl_CreateCommand(interp, Tk_PathName(textPtr->tkwin),
  258.         TextWidgetCmd, (ClientData) textPtr, (void (*)()) NULL);
  259.     if (ConfigureText(interp, textPtr, argc-2, argv+2, 0) != TCL_OK) {
  260.     Tk_DestroyWindow(textPtr->tkwin);
  261.     return TCL_ERROR;
  262.     }
  263.     Tk_CreateFocusHandler(textPtr->tkwin, TextFocusProc, (ClientData) textPtr);
  264.     interp->result = Tk_PathName(textPtr->tkwin);
  265.  
  266.     return TCL_OK;
  267. }
  268.  
  269. /*
  270.  *--------------------------------------------------------------
  271.  *
  272.  * TextWidgetCmd --
  273.  *
  274.  *    This procedure is invoked to process the Tcl command
  275.  *    that corresponds to a text widget.  See the user
  276.  *    documentation for details on what it does.
  277.  *
  278.  * Results:
  279.  *    A standard Tcl result.
  280.  *
  281.  * Side effects:
  282.  *    See the user documentation.
  283.  *
  284.  *--------------------------------------------------------------
  285.  */
  286.  
  287. static int
  288. TextWidgetCmd(clientData, interp, argc, argv)
  289.     ClientData clientData;    /* Information about text widget. */
  290.     Tcl_Interp *interp;        /* Current interpreter. */
  291.     int argc;            /* Number of arguments. */
  292.     char **argv;        /* Argument strings. */
  293. {
  294.     register TkText *textPtr = (TkText *) clientData;
  295.     int result = TCL_OK;
  296.     int length;
  297.     char c;
  298.     int line1, line2, ch1, ch2;
  299.  
  300.     if (argc < 2) {
  301.     Tcl_AppendResult(interp, "wrong # args: should be \"",
  302.         argv[0], " option ?arg arg ...?\"", (char *) NULL);
  303.     return TCL_ERROR;
  304.     }
  305.     Tk_Preserve((ClientData) textPtr);
  306.     c = argv[1][0];
  307.     length = strlen(argv[1]);
  308.     if ((c == 'c') && (strncmp(argv[1], "compare", length) == 0)
  309.         && (length >= 3)) {
  310.     int less, equal, greater, value;
  311.     char *p;
  312.  
  313.     if (argc != 5) {
  314.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  315.             argv[0], " compare index1 op index2\"", (char *) NULL);
  316.         result = TCL_ERROR;
  317.         goto done;
  318.     }
  319.     if ((TkTextGetIndex(interp, textPtr, argv[2], &line1, &ch1) != TCL_OK)
  320.         || (TkTextGetIndex(interp, textPtr, argv[4], &line2, &ch2)
  321.         != TCL_OK)) {
  322.         result = TCL_ERROR;
  323.         goto done;
  324.     }
  325.     less = equal = greater = 0;
  326.     if (line1 < line2) {
  327.         less = 1;
  328.     } else if (line1 > line2) {
  329.         greater = 1;
  330.     } else {
  331.         if (ch1 < ch2) {
  332.         less = 1;
  333.         } else if (ch1 > ch2) {
  334.         greater = 1;
  335.         } else {
  336.         equal = 1;
  337.         }
  338.     }
  339.     p = argv[3];
  340.     if (p[0] == '<') {
  341.         value = less;
  342.         if ((p[1] == '=') && (p[2] == 0)) {
  343.         value = less || equal;
  344.         } else if (p[1] != 0) {
  345.         compareError:
  346.         Tcl_AppendResult(interp, "bad comparison operator \"",
  347.             argv[3], "\": must be <, <=, ==, >=, >, or !=",
  348.             (char *) NULL);
  349.         result = TCL_ERROR;
  350.         goto done;
  351.         }
  352.     } else if (p[0] == '>') {
  353.         value = greater;
  354.         if ((p[1] == '=') && (p[2] == 0)) {
  355.         value = greater || equal;
  356.         } else if (p[1] != 0) {
  357.         goto compareError;
  358.         }
  359.     } else if ((p[0] == '=') && (p[1] == '=') && (p[2] == 0)) {
  360.         value = equal;
  361.     } else if ((p[0] == '!') && (p[1] == '=') && (p[2] == 0)) {
  362.         value = !equal;
  363.     } else {
  364.         goto compareError;
  365.     }
  366.     interp->result = (value) ? "1" : "0";
  367.     } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)
  368.         && (length >= 3)) {
  369.     if (argc == 2) {
  370.         result = Tk_ConfigureInfo(interp, textPtr->tkwin, configSpecs,
  371.             (char *) textPtr, (char *) NULL, 0);
  372.     } else if (argc == 3) {
  373.         result = Tk_ConfigureInfo(interp, textPtr->tkwin, configSpecs,
  374.             (char *) textPtr, argv[2], 0);
  375.     } else {
  376.         result = ConfigureText(interp, textPtr, argc-2, argv+2,
  377.             TK_CONFIG_ARGV_ONLY);
  378.     }
  379.     } else if ((c == 'd') && (strncmp(argv[1], "debug", length) == 0)
  380.         && (length >= 3)) {
  381.     if (argc > 3) {
  382.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  383.             argv[0], " debug ?on|off?\"", (char *) NULL);
  384.         result = TCL_ERROR;
  385.         goto done;
  386.     }
  387.     if (argc == 2) {
  388.         interp->result = (tkBTreeDebug) ? "on" : "off";
  389.     } else {
  390.         if (Tcl_GetBoolean(interp, argv[2], &tkBTreeDebug) != TCL_OK) {
  391.         result = TCL_ERROR;
  392.         goto done;
  393.         }
  394.     }
  395.     } else if ((c == 'd') && (strncmp(argv[1], "delete", length) == 0)
  396.         && (length >= 3)) {
  397.     if ((argc != 3) && (argc != 4)) {
  398.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  399.             argv[0], " delete index1 ?index2?\"", (char *) NULL);
  400.         result = TCL_ERROR;
  401.         goto done;
  402.     }
  403.     if (TkTextGetIndex(interp, textPtr, argv[2], &line1, &ch1) != TCL_OK) {
  404.         result = TCL_ERROR;
  405.         goto done;
  406.     }
  407.     if (argc == 3) {
  408.         line2 = line1;
  409.         ch2 = ch1+1;
  410.     } else if (TkTextGetIndex(interp, textPtr, argv[3], &line2, &ch2)
  411.         != TCL_OK) {
  412.         result = TCL_ERROR;
  413.         goto done;
  414.     }
  415.     if (textPtr->state == tkTextNormalUid) {
  416.         DeleteChars(textPtr, line1, ch1, line2, ch2);
  417.     }
  418.     } else if ((c == 'g') && (strncmp(argv[1], "get", length) == 0)) {
  419.     register TkTextLine *linePtr;
  420.  
  421.     if ((argc != 3) && (argc != 4)) {
  422.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  423.             argv[0], " get index1 ?index2?\"", (char *) NULL);
  424.         result = TCL_ERROR;
  425.         goto done;
  426.     }
  427.     if (TkTextGetIndex(interp, textPtr, argv[2], &line1, &ch1) != TCL_OK) {
  428.         result = TCL_ERROR;
  429.         goto done;
  430.     }
  431.     if (argc == 3) {
  432.         line2 = line1;
  433.         ch2 = ch1+1;
  434.     } else if (TkTextGetIndex(interp, textPtr, argv[3], &line2, &ch2)
  435.         != TCL_OK) {
  436.         result = TCL_ERROR;
  437.         goto done;
  438.     }
  439.     if (line1 < 0) {
  440.         line1 = 0;
  441.         ch1 = 0;
  442.     }
  443.     for (linePtr = TkBTreeFindLine(textPtr->tree, line1);
  444.         (linePtr != NULL) && (line1 <= line2);
  445.         linePtr = TkBTreeNextLine(linePtr), line1++, ch1 = 0) {
  446.         int savedChar, last;
  447.  
  448.         if (line1 == line2) {
  449.         last = ch2;
  450.         if (last > linePtr->numBytes) {
  451.             last = linePtr->numBytes;
  452.         }
  453.         } else {
  454.         last = linePtr->numBytes;
  455.         }
  456.         if (ch1 >= last) {
  457.         continue;
  458.         }
  459.         savedChar = linePtr->bytes[last];
  460.         linePtr->bytes[last] = 0;
  461.         Tcl_AppendResult(interp, linePtr->bytes+ch1, (char *) NULL);
  462.         linePtr->bytes[last] = savedChar;
  463.     }
  464.     } else if ((c == 'i') && (strncmp(argv[1], "index", length) == 0)
  465.         && (length >= 3)) {
  466.     if (argc != 3) {
  467.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  468.             argv[0], " index index\"",
  469.             (char *) NULL);
  470.         result = TCL_ERROR;
  471.         goto done;
  472.     }
  473.     if (TkTextGetIndex(interp, textPtr, argv[2], &line1, &ch1) != TCL_OK) {
  474.         result = TCL_ERROR;
  475.         goto done;
  476.     }
  477.     TkTextPrintIndex(line1, ch1, interp->result);
  478.     } else if ((c == 'i') && (strncmp(argv[1], "insert", length) == 0)
  479.         && (length >= 3)) {
  480.     if (argc != 4) {
  481.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  482.             argv[0], " insert index chars ?chars ...?\"",
  483.             (char *) NULL);
  484.         result = TCL_ERROR;
  485.         goto done;
  486.     }
  487.     if (TkTextGetIndex(interp, textPtr, argv[2], &line1, &ch1) != TCL_OK) {
  488.         result = TCL_ERROR;
  489.         goto done;
  490.     }
  491.     if (textPtr->state == tkTextNormalUid) {
  492.         InsertChars(textPtr, line1, ch1, argv[3]);
  493.     }
  494.     } else if ((c == 'm') && (strncmp(argv[1], "mark", length) == 0)) {
  495.     result = TextMarkCmd(textPtr, interp, argc, argv);
  496.     } else if ((c == 's') && (strcmp(argv[1], "scan") == 0)) {
  497.     result = TextScanCmd(textPtr, interp, argc, argv);
  498.     } else if ((c == 't') && (strcmp(argv[1], "tag") == 0)) {
  499.     result = TkTextTagCmd(textPtr, interp, argc, argv);
  500.     } else if ((c == 'y') && (strncmp(argv[1], "yview", length) == 0)) {
  501.     int numLines, pickPlace;
  502.  
  503.     if (argc < 3) {
  504.         yviewSyntax:
  505.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  506.             argv[0], " yview ?-pickplace? lineNum|index\"",
  507.             (char *) NULL);
  508.         result = TCL_ERROR;
  509.         goto done;
  510.     }
  511.     pickPlace = 0;
  512.     if (argv[2][0] == '-') {
  513.         int switchLength;
  514.  
  515.         switchLength = strlen(argv[2]);
  516.         if ((switchLength >= 2)
  517.             && (strncmp(argv[2], "-pickplace", switchLength) == 0)) {
  518.         pickPlace = 1;
  519.         }
  520.     }
  521.     if ((pickPlace+3) != argc) {
  522.         goto yviewSyntax;
  523.     }
  524.     if (Tcl_GetInt(interp, argv[2+pickPlace], &line1) != TCL_OK) {
  525.         Tcl_ResetResult(interp);
  526.         if (TkTextGetIndex(interp, textPtr, argv[2+pickPlace],
  527.             &line1, &ch1) != TCL_OK) {
  528.         result = TCL_ERROR;
  529.         goto done;
  530.         }
  531.     }
  532.     numLines = TkBTreeNumLines(textPtr->tree);
  533.     if (line1 >= numLines) {
  534.         line1 = numLines-1;
  535.     }
  536.     if (line1 < 0) {
  537.         line1 = 0;
  538.     }
  539.     TkTextSetView(textPtr, line1, pickPlace);
  540.     } else {
  541.     Tcl_AppendResult(interp, "bad option \"", argv[1],
  542.         "\":  must be compare, configure, debug, delete, get, ",
  543.         "index, insert, mark, scan, tag, or yview",
  544.         (char *) NULL);
  545.     result = TCL_ERROR;
  546.     }
  547.  
  548.     done:
  549.     Tk_Release((ClientData) textPtr);
  550.     return result;
  551. }
  552.  
  553. /*
  554.  *----------------------------------------------------------------------
  555.  *
  556.  * DestroyText --
  557.  *
  558.  *    This procedure is invoked by Tk_EventuallyFree or Tk_Release
  559.  *    to clean up the internal structure of a text at a safe time
  560.  *    (when no-one is using it anymore).
  561.  *
  562.  * Results:
  563.  *    None.
  564.  *
  565.  * Side effects:
  566.  *    Everything associated with the text is freed up.
  567.  *
  568.  *----------------------------------------------------------------------
  569.  */
  570.  
  571. static void
  572. DestroyText(clientData)
  573.     ClientData clientData;    /* Info about text widget. */
  574. {
  575.     register TkText *textPtr = (TkText *) clientData;
  576.     Tcl_HashSearch search;
  577.     Tcl_HashEntry *hPtr;
  578.     TkTextTag *tagPtr;
  579.  
  580.     TkBTreeDestroy(textPtr->tree);
  581.     for (hPtr = Tcl_FirstHashEntry(&textPtr->tagTable, &search);
  582.         hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
  583.     tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr);
  584.     TkTextFreeTag(tagPtr);
  585.     }
  586.     Tcl_DeleteHashTable(&textPtr->tagTable);
  587.     for (hPtr = Tcl_FirstHashEntry(&textPtr->markTable, &search);
  588.         hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
  589.     ckfree((char *) Tcl_GetHashValue(hPtr));
  590.     }
  591.     Tcl_DeleteHashTable(&textPtr->markTable);
  592.     if (textPtr->border != NULL) {
  593.     Tk_Free3DBorder(textPtr->border);
  594.     }
  595.     if (textPtr->cursor != None) {
  596.     Tk_FreeCursor(textPtr->cursor);
  597.     }
  598.     if (textPtr->fgColor != NULL) {
  599.     Tk_FreeColor(textPtr->fgColor);
  600.     }
  601.     if (textPtr->fontPtr != NULL) {
  602.     Tk_FreeFontStruct(textPtr->fontPtr);
  603.     }
  604.     TkTextFreeDInfo(textPtr);
  605.  
  606.     /*
  607.      * NOTE: do NOT free up selBorder or selFgColorPtr:  they are
  608.      * duplicates of information in the "sel" tag, which was freed
  609.      * up as part of deleting the tags above.
  610.      */
  611.  
  612.     if (textPtr->insertBorder != NULL) {
  613.     Tk_Free3DBorder(textPtr->insertBorder);
  614.     }
  615.     if (textPtr->insertBlinkHandler != NULL) {
  616.     Tk_DeleteTimerHandler(textPtr->insertBlinkHandler);
  617.     }
  618.     if (textPtr->bindingTable != NULL) {
  619.     Tk_DeleteBindingTable(textPtr->bindingTable);
  620.     }
  621.     if (textPtr->yScrollCmd != NULL) {
  622.     ckfree(textPtr->yScrollCmd);
  623.     }
  624.     ckfree((char *) textPtr);
  625. }
  626.  
  627. /*
  628.  *----------------------------------------------------------------------
  629.  *
  630.  * ConfigureText --
  631.  *
  632.  *    This procedure is called to process an argv/argc list, plus
  633.  *    the Tk option database, in order to configure (or
  634.  *    reconfigure) a text widget.
  635.  *
  636.  * Results:
  637.  *    The return value is a standard Tcl result.  If TCL_ERROR is
  638.  *    returned, then interp->result contains an error message.
  639.  *
  640.  * Side effects:
  641.  *    Configuration information, such as text string, colors, font,
  642.  *    etc. get set for textPtr;  old resources get freed, if there
  643.  *    were any.
  644.  *
  645.  *----------------------------------------------------------------------
  646.  */
  647.  
  648. static int
  649. ConfigureText(interp, textPtr, argc, argv, flags)
  650.     Tcl_Interp *interp;        /* Used for error reporting. */
  651.     register TkText *textPtr;    /* Information about widget;  may or may
  652.                  * not already have values for some fields. */
  653.     int argc;            /* Number of valid entries in argv. */
  654.     char **argv;        /* Arguments. */
  655.     int flags;            /* Flags to pass to Tk_ConfigureWidget. */
  656. {
  657.     int oldExport = textPtr->exportSelection;
  658.     int charWidth, charHeight;
  659.  
  660.     if (Tk_ConfigureWidget(interp, textPtr->tkwin, configSpecs,
  661.         argc, argv, (char *) textPtr, flags) != TCL_OK) {
  662.     return TCL_ERROR;
  663.     }
  664.  
  665.     /*
  666.      * A few other options also need special processing, such as parsing
  667.      * the geometry and setting the background from a 3-D border.
  668.      */
  669.  
  670.     if ((textPtr->state != tkTextNormalUid)
  671.         && (textPtr->state != tkTextDisabledUid)) {
  672.     Tcl_AppendResult(interp, "bad state value \"", textPtr->state,
  673.         "\":  must be normal or disabled", (char *) NULL);
  674.     textPtr->state = tkTextNormalUid;
  675.     return TCL_ERROR;
  676.     }
  677.  
  678.     if ((textPtr->wrapMode != tkTextCharUid)
  679.         && (textPtr->wrapMode != tkTextNoneUid)
  680.         && (textPtr->wrapMode != tkTextWordUid)) {
  681.     Tcl_AppendResult(interp, "bad wrap mode \"", textPtr->state,
  682.         "\":  must be char, none, or word", (char *) NULL);
  683.     textPtr->wrapMode = tkTextCharUid;
  684.     return TCL_ERROR;
  685.     }
  686.  
  687.     Tk_SetBackgroundFromBorder(textPtr->tkwin, textPtr->border);
  688.     Tk_SetInternalBorder(textPtr->tkwin, textPtr->borderWidth);
  689.     Tk_GeometryRequest(textPtr->tkwin, 200, 100);
  690.  
  691.     /*
  692.      * Make sure that configuration options are properly mirrored
  693.      * between the widget record and the "sel" tags.  NOTE: we don't
  694.      * have to free up information during the mirroring;  old
  695.      * information was freed when it was replaced in the widget
  696.      * record.
  697.      */
  698.  
  699.     textPtr->selTagPtr->border = textPtr->selBorder;
  700.     textPtr->selTagPtr->borderWidth = textPtr->selBorderWidth;
  701.     textPtr->selTagPtr->fgColor = textPtr->selFgColorPtr;
  702.  
  703.     /*
  704.      * Claim the selection if we've suddenly started exporting it and there
  705.      * are tagged characters.
  706.      */
  707.  
  708.     if (textPtr->exportSelection && (!oldExport)) {
  709.     TkTextSearch search;
  710.  
  711.     TkBTreeStartSearch(textPtr->tree, 0, 0, TkBTreeNumLines(textPtr->tree),
  712.         0, textPtr->selTagPtr, &search);
  713.     if (TkBTreeNextTag(&search)) {
  714.         Tk_OwnSelection(textPtr->tkwin, TkTextLostSelection,
  715.             (ClientData) textPtr);
  716.         textPtr->flags |= GOT_SELECTION;
  717.     }
  718.     }
  719.  
  720.     /*
  721.      * Register the desired geometry for the window, and arrange for
  722.      * the window to be redisplayed.
  723.      */
  724.  
  725.     if (textPtr->width <= 0) {
  726.     textPtr->width = 1;
  727.     }
  728.     if (textPtr->height <= 0) {
  729.     textPtr->height = 1;
  730.     }
  731.     charWidth = XTextWidth(textPtr->fontPtr, "0", 1);
  732.     charHeight = (textPtr->fontPtr->ascent + textPtr->fontPtr->descent);
  733.     Tk_GeometryRequest(textPtr->tkwin,
  734.         textPtr->width * charWidth + 2*textPtr->borderWidth
  735.             + 2*textPtr->padX,
  736.         textPtr->height * charHeight + 2*textPtr->borderWidth
  737.             + 2*textPtr->padX);
  738.     Tk_SetInternalBorder(textPtr->tkwin, textPtr->borderWidth);
  739.     if (textPtr->setGrid) {
  740.     Tk_SetGrid(textPtr->tkwin, textPtr->width, textPtr->height,
  741.         charWidth, charHeight);
  742.     }
  743.  
  744.     TkTextRelayoutWindow(textPtr);
  745.     return TCL_OK;
  746. }
  747.  
  748. /*
  749.  *--------------------------------------------------------------
  750.  *
  751.  * TextEventProc --
  752.  *
  753.  *    This procedure is invoked by the Tk dispatcher on
  754.  *    structure changes to a text.  For texts with 3D
  755.  *    borders, this procedure is also invoked for exposures.
  756.  *
  757.  * Results:
  758.  *    None.
  759.  *
  760.  * Side effects:
  761.  *    When the window gets deleted, internal structures get
  762.  *    cleaned up.  When it gets exposed, it is redisplayed.
  763.  *
  764.  *--------------------------------------------------------------
  765.  */
  766.  
  767. static void
  768. TextEventProc(clientData, eventPtr)
  769.     ClientData clientData;    /* Information about window. */
  770.     register XEvent *eventPtr;    /* Information about event. */
  771. {
  772.     register TkText *textPtr = (TkText *) clientData;
  773.  
  774.     if (eventPtr->type == Expose) {
  775.     TkTextRedrawRegion(textPtr, eventPtr->xexpose.x,
  776.         eventPtr->xexpose.y, eventPtr->xexpose.width,
  777.         eventPtr->xexpose.height);
  778.     } else if (eventPtr->type == ConfigureNotify) {
  779.     if ((textPtr->prevWidth != Tk_Width(textPtr->tkwin))
  780.         || (textPtr->prevHeight != Tk_Height(textPtr->tkwin))) {
  781.         TkTextRelayoutWindow(textPtr);
  782.     }
  783.     } else if (eventPtr->type == DestroyNotify) {
  784.     Tcl_DeleteCommand(textPtr->interp, Tk_PathName(textPtr->tkwin));
  785.     textPtr->tkwin = NULL;
  786.     Tk_EventuallyFree((ClientData) textPtr, DestroyText);
  787.     }
  788. }
  789.  
  790. /*
  791.  *----------------------------------------------------------------------
  792.  *
  793.  * InsertChars --
  794.  *
  795.  *    This procedure implements most of the functionality of the
  796.  *    "insert" widget command.
  797.  *
  798.  * Results:
  799.  *    None.
  800.  *
  801.  * Side effects:
  802.  *    The characters in "string" get added to the text just before
  803.  *    the character indicated by "line" and "ch".
  804.  *
  805.  *----------------------------------------------------------------------
  806.  */
  807.  
  808. static void
  809. InsertChars(textPtr, line, ch, string)
  810.     TkText *textPtr;        /* Overall information about text widget. */
  811.     int line, ch;        /* Identifies character just before which
  812.                  * new information is to be inserted. */
  813.     char *string;        /* Null-terminated string containing new
  814.                  * information to add to text. */
  815. {
  816.     register TkTextLine *linePtr;
  817.  
  818.     /*
  819.      * Locate the line where the insertion will occur.
  820.      */
  821.  
  822.     linePtr = TkTextRoundIndex(textPtr, &line, &ch);
  823.  
  824.     /*
  825.      * Notify the display module that lines are about to change, then do
  826.      * the insertion.
  827.      */
  828.  
  829.     TkTextLinesChanged(textPtr, line, line);
  830.     TkBTreeInsertChars(textPtr->tree, linePtr, ch, string);
  831.  
  832.     /*
  833.      * If the line containing the insertion point was textPtr->topLinePtr,
  834.      * we must reset this pointer since the line structure was re-allocated.
  835.      */
  836.  
  837.     if (linePtr == textPtr->topLinePtr) {
  838.     TkTextSetView(textPtr, line, 0);
  839.     }
  840.  
  841.     /*
  842.      * Invalidate any selection retrievals in progress.
  843.      */
  844.  
  845.     textPtr->selOffset = -1;
  846. }
  847.  
  848. /*
  849.  *----------------------------------------------------------------------
  850.  *
  851.  * DeleteChars --
  852.  *
  853.  *    This procedure implements most of the functionality of the
  854.  *    "delete" widget command.
  855.  *
  856.  * Results:
  857.  *    None.
  858.  *
  859.  * Side effects:
  860.  *    None.
  861.  *
  862.  *----------------------------------------------------------------------
  863.  */
  864.  
  865. static void
  866. DeleteChars(textPtr, line1, ch1, line2, ch2)
  867.     TkText *textPtr;        /* Overall information about text widget. */
  868.     int line1, ch1;        /* Position of first character to delete. */
  869.     int line2, ch2;        /* Position of character just after last
  870.                  * one to delete. */
  871. {
  872.     register TkTextLine *line1Ptr, *line2Ptr;
  873.     int numLines, topLine;
  874.  
  875.     /*
  876.      * The loop below is needed because a LeaveNotify event may be
  877.      * generated on the current charcter if it's about to be deleted.
  878.      * If this happens, then the bindings that trigger could modify
  879.      * the text, invalidating the range information computed here.
  880.      * So, go back and recompute all the range information after
  881.      * synthesizing a leave event.
  882.      */
  883.  
  884.     while (1) {
  885.  
  886.     /*
  887.      * Locate the starting and ending lines for the deletion and adjust
  888.      * the endpoints if necessary to ensure that they are within valid
  889.      * ranges.  Adjust the deletion range if necessary to ensure that the
  890.      * text (and each invidiual line) always ends in a newline.
  891.      */
  892.     
  893.     numLines = TkBTreeNumLines(textPtr->tree);
  894.     line1Ptr = TkTextRoundIndex(textPtr, &line1, &ch1);
  895.     if (line2 < 0) {
  896.         return;
  897.     } else if (line2 >= numLines) {
  898.         line2 = numLines-1;
  899.         line2Ptr = TkBTreeFindLine(textPtr->tree, line2);
  900.         ch2 = line2Ptr->numBytes;
  901.     } else {
  902.         line2Ptr = TkBTreeFindLine(textPtr->tree, line2);
  903.         if (ch2 < 0) {
  904.         ch2 = 0;
  905.         }
  906.     }
  907.     
  908.     /*
  909.      * If the deletion range ends after the last character of a line,
  910.      * do one of three things:
  911.      *
  912.      * (a) if line2Ptr isn't the last line of the text, just adjust the
  913.      *     ending point to be just before the 0th character of the next
  914.      *     line.
  915.      * (b) if ch1 is at the beginning of a line, then adjust line1Ptr and
  916.      *     ch1 to point just after the last character of the previous line.
  917.      * (c) otherwise, adjust ch2 so the final newline isn't deleted.
  918.      */
  919.     
  920.     if (ch2 >= line2Ptr->numBytes) {
  921.         if (line2 < (numLines-1)) {
  922.         line2++;
  923.         line2Ptr = TkBTreeNextLine(line2Ptr);
  924.         ch2 = 0;
  925.         } else {
  926.         ch2 = line2Ptr->numBytes-1;
  927.         if ((ch1 == 0) && (line1 > 0)) {
  928.             line1--;
  929.             line1Ptr = TkBTreeFindLine(textPtr->tree, line1);
  930.             ch1 = line1Ptr->numBytes;
  931.             ch2 = line2Ptr->numBytes;
  932.         } else {
  933.             ch2 = line2Ptr->numBytes-1;
  934.         }
  935.         }
  936.     }
  937.  
  938.     if ((line1 > line2) || ((line1 == line2) && (ch1 >= ch2))) {
  939.         return;
  940.     }
  941.  
  942.     /*
  943.      * If the current character is within the range being deleted,
  944.      * unpick it and synthesize a leave event for its tags, then
  945.      * go back and recompute the range ends.
  946.      */
  947.  
  948.     if (!(textPtr->flags & IN_CURRENT)) {
  949.         break;
  950.     }
  951.     if ((textPtr->currentAnnotPtr->linePtr == line1Ptr)
  952.         && (textPtr->currentAnnotPtr->ch < ch1)) {
  953.         break;
  954.     }
  955.     if ((textPtr->currentAnnotPtr->linePtr == line2Ptr)
  956.         && (textPtr->currentAnnotPtr->ch >= ch2)) {
  957.         break;
  958.     }
  959.     if (line2 > (line1+1)) {
  960.         int currentLine;
  961.  
  962.         currentLine = TkBTreeLineIndex(textPtr->currentAnnotPtr->linePtr);
  963.         if ((currentLine <= line1) || (currentLine >= line2)) {
  964.         break;
  965.         }
  966.     }
  967.     TkTextUnpickCurrent(textPtr);
  968.     }
  969.  
  970.     /*
  971.      * Tell the display what's about to happen so it can discard
  972.      * obsolete display information, then do the deletion.  Also,
  973.      * check to see if textPtr->topLinePtr is in the range of
  974.      * characters deleted.  If so, call the display module to reset
  975.      * it after doing the deletion.
  976.      */
  977.  
  978.     topLine = TkBTreeLineIndex(textPtr->topLinePtr);
  979.     TkTextLinesChanged(textPtr, line1, line2);
  980.     TkBTreeDeleteChars(textPtr->tree, line1Ptr, ch1, line2Ptr, ch2);
  981.     if ((topLine >= line1) && (topLine <= line2)) {
  982.     numLines = TkBTreeNumLines(textPtr->tree);
  983.     TkTextSetView(textPtr, (line1 > (numLines-1)) ? (numLines-1) : line1,
  984.         0);
  985.     }
  986.  
  987.     /*
  988.      * Invalidate any selection retrievals in progress.
  989.      */
  990.  
  991.     textPtr->selOffset = -1;
  992. }
  993.  
  994. /*
  995.  *----------------------------------------------------------------------
  996.  *
  997.  * TextFetchSelection --
  998.  *
  999.  *    This procedure is called back by Tk when the selection is
  1000.  *    requested by someone.  It returns part or all of the selection
  1001.  *    in a buffer provided by the caller.
  1002.  *
  1003.  * Results:
  1004.  *    The return value is the number of non-NULL bytes stored
  1005.  *    at buffer.  Buffer is filled (or partially filled) with a
  1006.  *    NULL-terminated string containing part or all of the selection,
  1007.  *    as given by offset and maxBytes.
  1008.  *
  1009.  * Side effects:
  1010.  *    None.
  1011.  *
  1012.  *----------------------------------------------------------------------
  1013.  */
  1014.  
  1015. static int
  1016. TextFetchSelection(clientData, offset, buffer, maxBytes)
  1017.     ClientData clientData;        /* Information about text widget. */
  1018.     int offset;                /* Offset within selection of first
  1019.                      * character to be returned. */
  1020.     char *buffer;            /* Location in which to place
  1021.                      * selection. */
  1022.     int maxBytes;            /* Maximum number of bytes to place
  1023.                      * at buffer, not including terminating
  1024.                      * NULL character. */
  1025. {
  1026.     register TkText *textPtr = (TkText *) clientData;
  1027.     register TkTextLine *linePtr;
  1028.     int count, chunkSize;
  1029.     TkTextSearch search;
  1030.  
  1031.     if (!textPtr->exportSelection) {
  1032.     return -1;
  1033.     }
  1034.  
  1035.     /*
  1036.      * Find the beginning of the next range of selected text.  Note:  if
  1037.      * the selection is being retrieved in multiple pieces (offset != 0)
  1038.      * and some modification has been made to the text that affects the
  1039.      * selection (textPtr->selOffset != offset) then reject the selection
  1040.      * request (make 'em start over again).
  1041.      */
  1042.  
  1043.     if (offset == 0) {
  1044.     textPtr->selLine = 0;
  1045.     textPtr->selCh = 0;
  1046.     textPtr->selOffset = 0;
  1047.     } else if (textPtr->selOffset != offset) {
  1048.     return 0;
  1049.     }
  1050.     TkBTreeStartSearch(textPtr->tree, textPtr->selLine, textPtr->selCh+1,
  1051.         TkBTreeNumLines(textPtr->tree), 0, textPtr->selTagPtr, &search);
  1052.     if (!TkBTreeCharTagged(search.linePtr, textPtr->selCh,
  1053.         textPtr->selTagPtr)) {
  1054.     if (!TkBTreeNextTag(&search)) {
  1055.         if (offset == 0) {
  1056.         return -1;
  1057.         } else {
  1058.         return 0;
  1059.         }
  1060.     }
  1061.     textPtr->selLine = search.line1;
  1062.     textPtr->selCh = search.ch1;
  1063.     }
  1064.  
  1065.     /*
  1066.      * Each iteration through the outer loop below scans one selected range.
  1067.      * Each iteration through the nested loop scans one line in the
  1068.      * selected range.
  1069.      */
  1070.  
  1071.     count = 0;
  1072.     while (1) {
  1073.     linePtr = search.linePtr;
  1074.  
  1075.     /*
  1076.      * Find the end of the current range of selected text.
  1077.      */
  1078.  
  1079.     if (!TkBTreeNextTag(&search)) {
  1080.         panic("TextFetchSelection couldn't find end of range");
  1081.     }
  1082.  
  1083.     /*
  1084.      * Copy information from text lines into the buffer until
  1085.      * either we run out of space in the buffer or we get to
  1086.      * the end of this range of text.
  1087.      */
  1088.  
  1089.     while (1) {
  1090.         chunkSize = ((linePtr == search.linePtr) ? search.ch1
  1091.             : linePtr->numBytes) - textPtr->selCh;
  1092.         if (chunkSize > maxBytes) {
  1093.         chunkSize = maxBytes;
  1094.         }
  1095.         memcpy((VOID *) buffer, (VOID *) (linePtr->bytes + textPtr->selCh),
  1096.             chunkSize);
  1097.         buffer += chunkSize;
  1098.         maxBytes -= chunkSize;
  1099.         count += chunkSize;
  1100.         textPtr->selOffset += chunkSize;
  1101.         if (maxBytes == 0) {
  1102.         textPtr->selCh += chunkSize;
  1103.         goto done;
  1104.         }
  1105.         if (linePtr == search.linePtr) {
  1106.         break;
  1107.         }
  1108.         textPtr->selCh = 0;
  1109.         textPtr->selLine++;
  1110.         linePtr = TkBTreeNextLine(linePtr);
  1111.     }
  1112.  
  1113.     /*
  1114.      * Find the beginning of the next range of selected text.
  1115.      */
  1116.  
  1117.     if (!TkBTreeNextTag(&search)) {
  1118.         break;
  1119.     }
  1120.     textPtr->selLine = search.line1;
  1121.     textPtr->selCh = search.ch1;
  1122.     }
  1123.  
  1124.     done:
  1125.     *buffer = 0;
  1126.     return count;
  1127. }
  1128.  
  1129. /*
  1130.  *----------------------------------------------------------------------
  1131.  *
  1132.  * TkTextLostSelection --
  1133.  *
  1134.  *    This procedure is called back by Tk when the selection is
  1135.  *    grabbed away from a text widget.
  1136.  *
  1137.  * Results:
  1138.  *    None.
  1139.  *
  1140.  * Side effects:
  1141.  *    The "sel" tag is cleared from the window.
  1142.  *
  1143.  *----------------------------------------------------------------------
  1144.  */
  1145.  
  1146. void
  1147. TkTextLostSelection(clientData)
  1148.     ClientData clientData;        /* Information about text widget. */
  1149. {
  1150.     register TkText *textPtr = (TkText *) clientData;
  1151.  
  1152.     if (!textPtr->exportSelection) {
  1153.     return;
  1154.     }
  1155.  
  1156.     /*
  1157.      * Just remove the "sel" tag from everything in the widget.
  1158.      */
  1159.  
  1160.     TkTextRedrawTag(textPtr, 0, 0, TkBTreeNumLines(textPtr->tree),
  1161.         0, textPtr->selTagPtr, 1);
  1162.     TkBTreeTag(textPtr->tree, 0, 0, TkBTreeNumLines(textPtr->tree),
  1163.         0, textPtr->selTagPtr, 0);
  1164.     textPtr->flags &= ~GOT_SELECTION;
  1165. }
  1166.  
  1167. /*
  1168.  *--------------------------------------------------------------
  1169.  *
  1170.  * TextMarkCmd --
  1171.  *
  1172.  *    This procedure is invoked to process the "mark" options of
  1173.  *    the widget command for text widgets. See the user documentation
  1174.  *    for details on what it does.
  1175.  *
  1176.  * Results:
  1177.  *    A standard Tcl result.
  1178.  *
  1179.  * Side effects:
  1180.  *    See the user documentation.
  1181.  *
  1182.  *--------------------------------------------------------------
  1183.  */
  1184.  
  1185. static int
  1186. TextMarkCmd(textPtr, interp, argc, argv)
  1187.     register TkText *textPtr;    /* Information about text widget. */
  1188.     Tcl_Interp *interp;        /* Current interpreter. */
  1189.     int argc;            /* Number of arguments. */
  1190.     char **argv;        /* Argument strings.  Someone else has already
  1191.                  * parsed this command enough to know that
  1192.                  * argv[1] is "mark". */
  1193. {
  1194.     int length, line, ch, i;
  1195.     char c;
  1196.     Tcl_HashEntry *hPtr;
  1197.     TkAnnotation *markPtr;
  1198.     Tcl_HashSearch search;
  1199.  
  1200.     if (argc < 3) {
  1201.     Tcl_AppendResult(interp, "wrong # args: should be \"",
  1202.         argv[0], " mark option ?arg arg ...?\"", (char *) NULL);
  1203.     return TCL_ERROR;
  1204.     }
  1205.     c = argv[2][0];
  1206.     length = strlen(argv[2]);
  1207.     if ((c == 'n') && (strncmp(argv[2], "names", length) == 0)) {
  1208.     if (argc != 3) {
  1209.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  1210.             argv[0], " mark names\"", (char *) NULL);
  1211.         return TCL_ERROR;
  1212.     }
  1213.     for (hPtr = Tcl_FirstHashEntry(&textPtr->markTable, &search);
  1214.         hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
  1215.         Tcl_AppendElement(interp,
  1216.             Tcl_GetHashKey(&textPtr->markTable, hPtr), 0);
  1217.     }
  1218.     } else if ((c == 's') && (strncmp(argv[2], "set", length) == 0)) {
  1219.     if (argc != 5) {
  1220.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  1221.             argv[0], " mark set markName index\"", (char *) NULL);
  1222.         return TCL_ERROR;
  1223.     }
  1224.     if (TkTextGetIndex(interp, textPtr, argv[4], &line, &ch) != TCL_OK) {
  1225.         return TCL_ERROR;
  1226.     }
  1227.     TkTextSetMark(textPtr, argv[3], line, ch);
  1228.     } else if ((c == 'u') && (strncmp(argv[2], "unset", length) == 0)) {
  1229.     if (argc < 4) {
  1230.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  1231.             argv[0], " mark unset markName ?markName ...?\"",
  1232.             (char *) NULL);
  1233.         return TCL_ERROR;
  1234.     }
  1235.     for (i = 3; i < argc; i++) {
  1236.         hPtr = Tcl_FindHashEntry(&textPtr->markTable, argv[i]);
  1237.         if (hPtr != NULL) {
  1238.         markPtr = (TkAnnotation *) Tcl_GetHashValue(hPtr);
  1239.         if (markPtr == textPtr->insertAnnotPtr) {
  1240.             interp->result = "can't delete \"insert\" mark";
  1241.             return TCL_ERROR;
  1242.         }
  1243.         if (markPtr == textPtr->currentAnnotPtr) {
  1244.             interp->result = "can't delete \"current\" mark";
  1245.             return TCL_ERROR;
  1246.         }
  1247.         TkBTreeRemoveAnnotation(markPtr);
  1248.         Tcl_DeleteHashEntry(hPtr);
  1249.         ckfree((char *) markPtr);
  1250.         }
  1251.     }
  1252.     } else {
  1253.     Tcl_AppendResult(interp, "bad mark option \"", argv[2],
  1254.         "\":  must be names, set, or unset",
  1255.         (char *) NULL);
  1256.     return TCL_ERROR;
  1257.     }
  1258.     return TCL_OK;
  1259. }
  1260.  
  1261. /*
  1262.  *----------------------------------------------------------------------
  1263.  *
  1264.  * TkTextSetMark --
  1265.  *
  1266.  *    Set a mark to a particular position, creating a new mark if
  1267.  *    one doesn't already exist.
  1268.  *
  1269.  * Results:
  1270.  *    The return value is a pointer to the mark that was just set.
  1271.  *
  1272.  * Side effects:
  1273.  *    A new mark is created, or an existing mark is moved.
  1274.  *
  1275.  *----------------------------------------------------------------------
  1276.  */
  1277.  
  1278. TkAnnotation *
  1279. TkTextSetMark(textPtr, name, line, ch)
  1280.     TkText *textPtr;        /* Text widget in which to create mark. */
  1281.     char *name;            /* Name of mark to set. */
  1282.     int line;            /* Index of line at which to place mark. */
  1283.     int ch;            /* Index of character within line at which
  1284.                  * to place mark. */
  1285. {
  1286.     Tcl_HashEntry *hPtr;
  1287.     TkAnnotation *markPtr;
  1288.     int new;
  1289.  
  1290.     hPtr = Tcl_CreateHashEntry(&textPtr->markTable, name, &new);
  1291.     markPtr = (TkAnnotation *) Tcl_GetHashValue(hPtr);
  1292.     if (!new) {
  1293.     /*
  1294.      * If this is the insertion point that's being moved, be sure
  1295.      * to force a display update at the old position.
  1296.      */
  1297.  
  1298.     if (markPtr == textPtr->insertAnnotPtr) {
  1299.         int oldLine;
  1300.  
  1301.         oldLine = TkBTreeLineIndex(markPtr->linePtr);
  1302.         TkTextLinesChanged(textPtr, oldLine, oldLine);
  1303.     }
  1304.     TkBTreeRemoveAnnotation(markPtr);
  1305.     } else {
  1306.     markPtr = (TkAnnotation *) ckalloc(sizeof(TkAnnotation));
  1307.     markPtr->type = TK_ANNOT_MARK;
  1308.     markPtr->info.hPtr = hPtr;
  1309.     Tcl_SetHashValue(hPtr, markPtr);
  1310.     }
  1311.     if (line < 0) {
  1312.     line = 0;
  1313.     markPtr->ch = 0;
  1314.     } else if (ch < 0) {
  1315.     markPtr->ch = 0;
  1316.     } else {
  1317.     markPtr->ch = ch;
  1318.     }
  1319.     markPtr->linePtr = TkBTreeFindLine(textPtr->tree, line);
  1320.     if (markPtr->linePtr == NULL) {
  1321.     line = TkBTreeNumLines(textPtr->tree)-1;
  1322.     markPtr->linePtr = TkBTreeFindLine(textPtr->tree, line);
  1323.     markPtr->ch = markPtr->linePtr->numBytes-1;
  1324.     } else {
  1325.     if (markPtr->ch >= markPtr->linePtr->numBytes) {
  1326.         TkTextLine *nextLinePtr;
  1327.  
  1328.         nextLinePtr = TkBTreeNextLine(markPtr->linePtr);
  1329.         if (nextLinePtr == NULL) {
  1330.         markPtr->ch = markPtr->linePtr->numBytes-1;
  1331.         } else {
  1332.         markPtr->linePtr = nextLinePtr;
  1333.         line++;
  1334.         markPtr->ch = 0;
  1335.         }
  1336.     }
  1337.     }
  1338.     TkBTreeAddAnnotation(markPtr);
  1339.  
  1340.     /*
  1341.      * If the mark is the insertion cursor, then update the screen at the
  1342.      * mark's new location.
  1343.      */
  1344.  
  1345.     if (markPtr == textPtr->insertAnnotPtr) {
  1346.     TkTextLinesChanged(textPtr, line, line);
  1347.     }
  1348.     return markPtr;
  1349. }
  1350.  
  1351. /*
  1352.  *----------------------------------------------------------------------
  1353.  *
  1354.  * TextBlinkProc --
  1355.  *
  1356.  *    This procedure is called as a timer handler to blink the
  1357.  *    insertion cursor off and on.
  1358.  *
  1359.  * Results:
  1360.  *    None.
  1361.  *
  1362.  * Side effects:
  1363.  *    The cursor gets turned on or off, redisplay gets invoked,
  1364.  *    and this procedure reschedules itself.
  1365.  *
  1366.  *----------------------------------------------------------------------
  1367.  */
  1368.  
  1369. static void
  1370. TextBlinkProc(clientData)
  1371.     ClientData clientData;    /* Pointer to record describing text. */
  1372. {
  1373.     register TkText *textPtr = (TkText *) clientData;
  1374.     int lineNum;
  1375.  
  1376.     if (!(textPtr->flags & GOT_FOCUS) || (textPtr->insertOffTime == 0)) {
  1377.     return;
  1378.     }
  1379.     if (textPtr->flags & INSERT_ON) {
  1380.     textPtr->flags &= ~INSERT_ON;
  1381.     textPtr->insertBlinkHandler = Tk_CreateTimerHandler(
  1382.         textPtr->insertOffTime, TextBlinkProc, (ClientData) textPtr);
  1383.     } else {
  1384.     textPtr->flags |= INSERT_ON;
  1385.     textPtr->insertBlinkHandler = Tk_CreateTimerHandler(
  1386.         textPtr->insertOnTime, TextBlinkProc, (ClientData) textPtr);
  1387.     }
  1388.     lineNum = TkBTreeLineIndex(textPtr->insertAnnotPtr->linePtr);
  1389.     TkTextLinesChanged(textPtr, lineNum, lineNum);
  1390. }
  1391.  
  1392. /*
  1393.  *----------------------------------------------------------------------
  1394.  *
  1395.  * TextFocusProc --
  1396.  *
  1397.  *    This procedure is called whenever the entry gets or loses the
  1398.  *    input focus.  It's also called whenever the window is reconfigured
  1399.  *    while it has the focus.
  1400.  *
  1401.  * Results:
  1402.  *    None.
  1403.  *
  1404.  * Side effects:
  1405.  *    The cursor gets turned on or off.
  1406.  *
  1407.  *----------------------------------------------------------------------
  1408.  */
  1409.  
  1410. static void
  1411. TextFocusProc(clientData, gotFocus)
  1412.     ClientData clientData;    /* Pointer to structure describing text. */
  1413.     int gotFocus;        /* 1 means window is getting focus, 0 means
  1414.                  * it's losing it. */
  1415. {
  1416.     register TkText *textPtr = (TkText *) clientData;
  1417.     int lineNum;
  1418.  
  1419.     Tk_DeleteTimerHandler(textPtr->insertBlinkHandler);
  1420.     if (gotFocus) {
  1421.     textPtr->flags |= GOT_FOCUS | INSERT_ON;
  1422.     if (textPtr->insertOffTime != 0) {
  1423.         textPtr->insertBlinkHandler = Tk_CreateTimerHandler(
  1424.             textPtr->insertOnTime, TextBlinkProc,
  1425.             (ClientData) textPtr);
  1426.     }
  1427.     } else {
  1428.     textPtr->flags &= ~(GOT_FOCUS | INSERT_ON);
  1429.     textPtr->insertBlinkHandler = (Tk_TimerToken) NULL;
  1430.     }
  1431.     lineNum = TkBTreeLineIndex(textPtr->insertAnnotPtr->linePtr);
  1432.     TkTextLinesChanged(textPtr, lineNum, lineNum);
  1433. }
  1434.  
  1435. /*
  1436.  *--------------------------------------------------------------
  1437.  *
  1438.  * TextScanCmd --
  1439.  *
  1440.  *    This procedure is invoked to process the "scan" options of
  1441.  *    the widget command for text widgets. See the user documentation
  1442.  *    for details on what it does.
  1443.  *
  1444.  * Results:
  1445.  *    A standard Tcl result.
  1446.  *
  1447.  * Side effects:
  1448.  *    See the user documentation.
  1449.  *
  1450.  *--------------------------------------------------------------
  1451.  */
  1452.  
  1453. static int
  1454. TextScanCmd(textPtr, interp, argc, argv)
  1455.     register TkText *textPtr;    /* Information about text widget. */
  1456.     Tcl_Interp *interp;        /* Current interpreter. */
  1457.     int argc;            /* Number of arguments. */
  1458.     char **argv;        /* Argument strings.  Someone else has already
  1459.                  * parsed this command enough to know that
  1460.                  * argv[1] is "tag". */
  1461. {
  1462.     int length, y, line, lastLine;
  1463.     char c;
  1464.  
  1465.     if (argc != 4) {
  1466.     Tcl_AppendResult(interp, "wrong # args: should be \"",
  1467.         argv[0], " scan mark|dragto y\"", (char *) NULL);
  1468.     return TCL_ERROR;
  1469.     }
  1470.     if (Tcl_GetInt(interp, argv[3], &y) != TCL_OK) {
  1471.     return TCL_ERROR;
  1472.     }
  1473.     c = argv[2][0];
  1474.     length = strlen(argv[2]);
  1475.     if ((c == 'd') && (strncmp(argv[2], "dragto", length) == 0)) {
  1476.     /*
  1477.      * Amplify the difference between the current y position and the
  1478.      * mark position to compute how many lines up or down the view
  1479.      * should shift, then update the mark position to correspond to
  1480.      * the new view.  If we run off the top or bottom of the text,
  1481.      * reset the mark point so that the current position continues
  1482.      * to correspond to the edge of the window.  This means that the
  1483.      * picture will start dragging as soon as the mouse reverses
  1484.      * direction (without this reset, might have to slide mouse a
  1485.      * long ways back before the picture starts moving again).
  1486.      */
  1487.  
  1488.     line = textPtr->scanMarkLine + (10*(textPtr->scanMarkY - y))
  1489.         / (textPtr->fontPtr->ascent + textPtr->fontPtr->descent);
  1490.     lastLine = TkBTreeNumLines(textPtr->tree) - 1;
  1491.     if (line < 0) {
  1492.         textPtr->scanMarkLine = line = 0;
  1493.         textPtr->scanMarkY = y;
  1494.     } else if (line > lastLine) {
  1495.         textPtr->scanMarkLine = line = lastLine;
  1496.         textPtr->scanMarkY = y;
  1497.     }
  1498.     TkTextSetView(textPtr, line, 0);
  1499.     } else if ((c == 'm') && (strncmp(argv[2], "mark", length) == 0)) {
  1500.     textPtr->scanMarkLine = TkBTreeLineIndex(textPtr->topLinePtr);
  1501.     textPtr->scanMarkY = y;
  1502.     } else {
  1503.     Tcl_AppendResult(interp, "bad scan option \"", argv[2],
  1504.         "\":  must be mark or dragto", (char *) NULL);
  1505.     return TCL_ERROR;
  1506.     }
  1507.     return TCL_OK;
  1508. }
  1509.